home *** CD-ROM | disk | FTP | other *** search
/ The Datafile PD-CD 1 Issue 2 / PDCD-1 - Issue 02.iso / _utilities / utilities / 001 / fue / c / search < prev    next >
Text File  |  1991-04-05  |  64KB  |  1,844 lines

  1. /*
  2.  * The functions in this file implement commands that search in the forward
  3.  * and backward directions.
  4.  *
  5.  * Aug. 1986 John M. Gamble:
  6.  *      Made forward and reverse search use the same scan routine.
  7.  *
  8.  *      Added a limited number of regular expressions - 'any',
  9.  *      'character class', 'closure', 'beginning of line', and
  10.  *      'end of line'.
  11.  *
  12.  *      Replacement metacharacters will have to wait for a re-write of
  13.  *      the replaces function, and a new variation of ldelete().
  14.  *
  15.  *      For those curious as to my references, i made use of
  16.  *      Kernighan & Plauger's "Software Tools."
  17.  *      I deliberately did not look at any published grep or editor
  18.  *      source (aside from this one) for inspiration.  I did make use of
  19.  *      Allen Hollub's bitmap routines as published in Doctor Dobb's Journal,
  20.  *      June, 1985 and modified them for the limited needs of character class
  21.  *      matching.  Any inefficiences, bugs, stupid coding examples, etc.,
  22.  *      are therefore my own responsibility.
  23.  *
  24.  * April 1987: John M. Gamble
  25.  *      Deleted the "if (n == 0) n = 1;" statements in front of the
  26.  *      search/hunt routines.  Since we now use a do loop, these
  27.  *      checks are unnecessary.  Consolidated common code into the
  28.  *      function delins().  Renamed global mclen matchlen,
  29.  *      and added the globals matchline, matchoff, patmatch, and
  30.  *      mlenold.
  31.  *      This gave us the ability to unreplace regular expression searches,
  32.  *      and to put the matched string into an evironment variable.
  33.  *      SOON TO COME: Meta-replacement characters!
  34.  *
  35.  *      25-apr-87       DML
  36.  *      - cleaned up an unneccessary if/else in forwsearch() and
  37.  *        backsearch()
  38.  *      - savematch() failed to malloc room for the terminating byte
  39.  *        of the match string (stomp...stomp...). It does now. Also
  40.  *        it now returns gracefully if malloc fails
  41.  *
  42.  *      July 1987: John M. Gamble
  43.  *      Set the variables matchlen and matchoff in the 'unreplace'
  44.  *      section of replaces().  The function savematch() would
  45.  *      get confused if you replaced, unreplaced, then replaced
  46.  *      again (serves you right for being so wishy-washy...)
  47.  *
  48.  *      August 1987: John M. Gamble
  49.  *      Put in new function rmcstr() to create the replacement
  50.  *      meta-character array.  Modified delins() so that it knows
  51.  *      whether or not to make use of the array.  And, put in the
  52.  *      appropriate new structures and variables.
  53.  *
  54.  *      4 November 1987 Geoff Gibbs
  55.  *
  56.  *      Fast version using simplified version of Boyer and Moore
  57.  *      Software-Practice and Experience, vol 10, 501-506 (1980).
  58.  *      Mods to scanner() and readpattern(), and added fbound() and
  59.  *      setjtable().  Scanner() should be callable as before, provided
  60.  *      setjtable() has been called first.
  61.  *
  62.  *      December 1987: John M. Gamble
  63.  *      Made replaces() beep at you and stop if an empty string is
  64.  *      replaced.  This is possible in MAGIC mode, if you pick your
  65.  *      pattern incorrectly.
  66.  *      Fixed a subtle bug in the new Boyer-Moore routines, which caused
  67.  *      searches to fail occasionally.
  68.  *
  69.  *      12-apr-88 Daniel Lawrence
  70.  *      Fixed a bug in the replaces() which would misplace the point after
  71.  *      aborting a query-replace-string.
  72.  *
  73.  *      21-Sep-89 Mike Burrow (INMOS)
  74.  *      Added routines to search within folds, and if match is found, expand
  75.  *      the fold.
  76.  */
  77.  
  78. #include        <stdio.h>
  79. #include        "estruct.h"
  80. #include        "etype.h"
  81. #include        "edef.h"
  82. #include        "elang.h"
  83.  
  84. static int patlenadd;
  85. static int deltaf[HICHAR], deltab[HICHAR];
  86. static int lastchfjump, lastchbjump;
  87. static int enterfolds;  /* MJB: 21-Sep-89 */
  88.  
  89. /* Function prototypes */
  90. PASCAL NEAR fsearch();
  91. PASCAL NEAR bksearch();
  92.  
  93. /*
  94.  * searchffolds -- Search forwards into folds, open those folds
  95.  *                 required to display the match. MJB: 21-Sep-89.
  96.  */
  97. PASCAL NEAR searchffold(f, n)
  98. int f, n;       /* default flag / numeric argument */
  99. {
  100.         enterfolds = TRUE;
  101.         return(fsearch(f, n));
  102. }
  103.  
  104.  
  105. /*
  106.  * forwsearch -- Search forwards ignore folds. MJB: 21-Sep-89.
  107.  */
  108. PASCAL NEAR forwsearch(f, n)
  109. int f, n;       /* default flag / numeric argument */
  110. {
  111.         enterfolds = FALSE;
  112.         return(fsearch(f, n));
  113. }
  114.  
  115.  
  116. /*
  117.  * searchbfolds -- Search backwards into folds, open those folds
  118.  *                 required to display the match. MJB: 21-Sep-89.
  119.  */
  120. PASCAL NEAR searchbfold(f, n)
  121. int f, n;       /* default flag / numeric argument */
  122. {
  123.         enterfolds = TRUE;
  124.         return(bksearch(f, n));
  125. }
  126.  
  127.  
  128. /*
  129.  * backsearch -- Search backwards ignore folds. MJB: 21-Sep-89.
  130.  */
  131. PASCAL NEAR backsearch(f, n)
  132. int f, n;       /* default flag / numeric argument */
  133. {
  134.         enterfolds = FALSE;
  135.         return(bksearch(f, n));
  136. }
  137.  
  138.  
  139. /*
  140.  * fsearch -- Search forward.  Get a search string from the user, and
  141.  *      search for the string.  If found, reset the "." to be just after
  142.  *      the match string, and (perhaps) repaint the display.
  143.  */
  144. PASCAL NEAR fsearch(f, n)
  145. int f, n;       /* default flag / numeric argument */
  146. {
  147.         register int status = TRUE;
  148.  
  149.         /* If n is negative, search backwards.
  150.          * Otherwise proceed by asking for the search string.
  151.          */
  152.         if (n < 0)
  153.                 return(bksearch(f, -n));
  154.  
  155.         /* Ask the user for the text of a pattern.  If the
  156.          * response is TRUE (responses other than FALSE are
  157.          * possible), search for the pattern for as long as
  158.          * n is positive (n == 0 will go through once, which
  159.          * is just fine).
  160.          */
  161.         if ((status = readpattern(TEXT78, &pat[0], TRUE)) == TRUE) {
  162. /*                                "Search" */
  163.                 do {
  164. #if     MAGIC
  165.                         if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
  166.                                 status = mcscanner(&mcpat[0], FORWARD, PTEND);
  167.                         else
  168. #endif
  169.                                 status = scanner(&pat[0], FORWARD, PTEND);
  170.                 } while ((--n > 0) && status);
  171.  
  172.                 /* Save away the match, or complain
  173.                  * if not there.
  174.                  */
  175.                 if (status != TRUE)
  176.                         mlwrite(TEXT79);
  177. /*                              "Not found" */
  178.         }
  179.         return(status);
  180. }
  181.  
  182. /*
  183.  * forwhunt -- Search forward for a previously acquired search string.
  184.  *      If found, reset the "." to be just after the match string,
  185.  *      and (perhaps) repaint the display.
  186.  */
  187.  
  188. PASCAL NEAR forwhunt(f, n)
  189. int f, n;       /* default flag / numeric argument */
  190. {
  191.         register int status = TRUE;
  192.  
  193.         if (n < 0)              /* search backwards */
  194.                 return(backhunt(f, -n));
  195.  
  196.         /* Make sure a pattern exists, or that we didn't switch
  197.          * into MAGIC mode until after we entered the pattern.
  198.          */
  199.         if (pat[0] == '\0')
  200.         {
  201.                 mlwrite(TEXT80);
  202. /*                      "No pattern set" */
  203.                 return FALSE;
  204.         }
  205. #if     MAGIC
  206.         if ((curwp->w_bufp->b_mode & MDMAGIC) != 0 &&
  207.                  mcpat[0].mc_type == MCNIL)
  208.         {
  209.                 if (!mcstr())
  210.                         return FALSE;
  211.         }
  212. #endif
  213.  
  214.         /* Search for the pattern for as long as
  215.          * n is positive (n == 0 will go through once, which
  216.          * is just fine).
  217.          */
  218.         do
  219.         {
  220. #if     MAGIC
  221.                 if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
  222.                         status = mcscanner(&mcpat[0], FORWARD, PTEND);
  223.                 else
  224. #endif
  225.                         status = scanner(&pat[0], FORWARD, PTEND);
  226.         } while ((--n > 0) && status);
  227.  
  228.         /* Save away the match, or complain
  229.          * if not there.
  230.          */
  231.         if (status != TRUE)
  232.                 mlwrite(TEXT79);
  233. /*                      "Not found" */
  234.  
  235.         return(status);
  236. }
  237.  
  238. /*
  239.  * bksearch -- Reverse search.  Get a search string from the user, and
  240.  *      search, starting at "." and proceeding toward the front of the buffer.
  241.  *      If found "." is left pointing at the first character of the pattern
  242.  *      (the last character that was matched).
  243.  */
  244. PASCAL NEAR bksearch(f, n)
  245. int f, n;       /* default flag / numeric argument */
  246. {
  247.         register int status = TRUE;
  248.  
  249.         /* If n is negative, search forwards.
  250.          * Otherwise proceed by asking for the search string.
  251.          */
  252.         if (n < 0)
  253.                 return(fsearch(f, -n));
  254.  
  255.         /* Ask the user for the text of a pattern.  If the
  256.          * response is TRUE (responses other than FALSE are
  257.          * possible), search for the pattern for as long as
  258.          * n is positive (n == 0 will go through once, which
  259.          * is just fine).
  260.          */
  261.         if ((status = readpattern(TEXT81, &pat[0], TRUE)) == TRUE) {
  262. /*                                "Reverse search" */
  263.                 do {
  264. #if     MAGIC
  265.                         if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
  266.                                 status = mcscanner(&tapcm[0], REVERSE, PTBEG);
  267.                         else
  268. #endif
  269.                                 status = scanner(&tap[0], REVERSE, PTBEG);
  270.                 } while ((--n > 0) && status);
  271.  
  272.                 /* Save away the match, or complain
  273.                  * if not there.
  274.                  */
  275.                 if (status != TRUE)
  276.                         mlwrite(TEXT79);
  277. /*                              "Not found" */
  278.         }
  279.         return(status);
  280. }
  281.  
  282. /*
  283.  * backhunt -- Reverse search for a previously acquired search string,
  284.  *      starting at "." and proceeding toward the front of the buffer.
  285.  *      If found "." is left pointing at the first character of the pattern
  286.  *      (the last character that was matched).
  287.  */
  288. PASCAL NEAR backhunt(f, n)
  289. int f, n;       /* default flag / numeric argument */
  290. {
  291.         register int    status = TRUE;
  292.  
  293.         if (n < 0)
  294.                 return(forwhunt(f, -n));
  295.  
  296.         /* Make sure a pattern exists, or that we didn't switch
  297.          * into MAGIC mode until after we entered the pattern.
  298.          */
  299.         if (tap[0] == '\0')
  300.         {
  301.                 mlwrite(TEXT80);
  302. /*                      "No pattern set" */
  303.                 return FALSE;
  304.         }
  305. #if     MAGIC
  306.         if ((curwp->w_bufp->b_mode & MDMAGIC) != 0 &&
  307.                  tapcm[0].mc_type == MCNIL)
  308.         {
  309.                 if (!mcstr())
  310.                         return FALSE;
  311.         }
  312. #endif
  313.  
  314.         /* Go search for it for as long as
  315.          * n is positive (n == 0 will go through once, which
  316.          * is just fine).
  317.          */
  318.         do
  319.         {
  320. #if     MAGIC
  321.                 if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
  322.                         status = mcscanner(&tapcm[0], REVERSE, PTBEG);
  323.                 else
  324. #endif
  325.                         status = scanner(&tap[0], REVERSE, PTBEG);
  326.         } while ((--n > 0) && status);
  327.  
  328.         /* Save away the match, or complain
  329.          * if not there.
  330.          */
  331.         if (status != TRUE)
  332.                 mlwrite(TEXT79);
  333. /*                      "Not found" */
  334.  
  335.         return(status);
  336. }
  337.  
  338. #if     MAGIC
  339. /*
  340.  * mcscanner -- Search for a meta-pattern in either direction.  If found,
  341.  *      reset the "." to be at the start or just after the match string,
  342.  *      and (perhaps) repaint the display.
  343.  */
  344. int PASCAL NEAR mcscanner(mcpatrn, direct, beg_or_end)
  345. MC      *mcpatrn;       /* pointer into pattern */
  346. int     direct;         /* which way to go.*/
  347. int     beg_or_end;     /* put point at beginning or end of pattern.*/
  348. {
  349.         LINE *curline;                  /* current line during scan */
  350.         int curoff;                     /* position within current line */
  351.  
  352.         /* If we are going in reverse, then the 'end' is actually
  353.          * the beginning of the pattern.  Toggle it.
  354.          */
  355.         beg_or_end ^= direct;
  356.  
  357.         /*
  358.          * Save the old matchlen length, in case it is
  359.          * very different (closure) from the old length.
  360.          * This is important for query-replace undo
  361.          * command.
  362.          */
  363.         mlenold = matchlen;
  364.  
  365.         /* Setup local scan pointers to global ".".
  366.          */
  367.         curline = curwp->w_dotp;
  368.         curoff  = curwp->w_doto;
  369.  
  370.         /* Scan each character until we hit the head link record.
  371.          */
  372.         while (!boundry(curline, curoff, direct))
  373.         {
  374.                 /* Save the current position in case we need to
  375.                  * restore it on a match, and initialize matchlen to
  376.                  * zero in case we are doing a search for replacement.
  377.                  */
  378.                 matchline = curline;
  379.                 matchoff = curoff;
  380.                 matchlen = 0;
  381.  
  382.                 if (amatch(mcpatrn, direct, &curline, &curoff))
  383.                 {
  384.                         /* A SUCCESSFULL MATCH!!!
  385.                          * reset the global "." pointers.
  386.                          */
  387.                         if (beg_or_end == PTEND)        /* at end of string */
  388.                         {
  389.                                 curwp->w_dotp = curline;
  390.                                 curwp->w_doto = curoff;
  391.                         }
  392.                         else            /* at beginning of string */
  393.                         {
  394.                                 curwp->w_dotp = matchline;
  395.                                 curwp->w_doto = matchoff;
  396.                         }
  397.  
  398.                         if (enterfolds)
  399.                                 openoutfolds();
  400.  
  401.                         curwp->w_flag |= WFMOVE; /* flag that we have moved */
  402.                         savematch();
  403.                         return TRUE;
  404.                 }
  405.  
  406.                 /* Advance the cursor.
  407.                  */
  408.                 nextch(&curline, &curoff, direct);
  409.         }
  410.  
  411.         return FALSE;   /* We could not find a match.*/
  412. }
  413.  
  414. /*
  415.  * amatch -- Search for a meta-pattern in either direction.  Based on the
  416.  *      recursive routine amatch() (for "anchored match") in
  417.  *      Kernighan & Plauger's "Software Tools".
  418.  */
  419. int PASCAL NEAR amatch(mcptr, direct, pcwline, pcwoff)
  420. register MC     *mcptr; /* string to scan for */
  421. int             direct;         /* which way to go.*/
  422. LINE            **pcwline;      /* current line during scan */
  423. int             *pcwoff;        /* position within current line */
  424. {
  425.         register int c;                 /* character at current position */
  426.         LINE *curline;                  /* current line during scan */
  427.         int curoff;                     /* position within current line */
  428.         int nchars;
  429.  
  430.         /* Set up local scan pointers to ".", and get
  431.          * the current character.  Then loop around
  432.          * the pattern pointer until success or failure.
  433.          */
  434.         curline = *pcwline;
  435.         curoff = *pcwoff;
  436.  
  437.         /* The beginning-of-line and end-of-line metacharacters
  438.          * do not compare against characters, they compare
  439.          * against positions.
  440.          * BOL is guaranteed to be at the start of the pattern
  441.          * for forward searches, and at the end of the pattern
  442.          * for reverse searches.  The reverse is true for EOL.
  443.          * So, for a start, we check for them on entry.
  444.          */
  445.         if (mcptr->mc_type == BOL)
  446.         {
  447.                 if (curoff != 0)
  448.                         return FALSE;
  449.                 mcptr++;
  450.         }
  451.  
  452.         if (mcptr->mc_type == EOL)
  453.         {
  454.                 if (curoff != llength(curline))
  455.                         return FALSE;
  456.                 mcptr++;
  457.         }
  458.  
  459.         while (mcptr->mc_type != MCNIL)
  460.         {
  461.                 c = nextch(&curline, &curoff, direct);
  462.  
  463.                 if (mcptr->mc_type & CLOSURE)
  464.                 {
  465.                         /* Try to match as many characters as possible
  466.                          * against the current meta-character.  A
  467.                          * newline never matches a closure.
  468.                          */
  469.                         nchars = 0;
  470.                         while (c != '\r' && mceq(c, mcptr))
  471.                         {
  472.                                 c = nextch(&curline, &curoff, direct);
  473.                                 nchars++;
  474.                         }
  475.  
  476.                         /* We are now at the character that made us
  477.                          * fail.  Try to match the rest of the pattern.
  478.                          * Shrink the closure by one for each failure.
  479.                          * Since closure matches *zero* or more occurences
  480.                          * of a pattern, a match may start even if the
  481.                          * previous loop matched no characters.
  482.                          */
  483.                         mcptr++;
  484.  
  485.                         for (;;)
  486.                         {
  487.                                 c = nextch(&curline, &curoff, direct ^ REVERSE);
  488.  
  489.                                 if (amatch(mcptr, direct, &curline, &curoff))
  490.                                 {
  491.                                         matchlen += nchars;
  492.                                         goto success;
  493.                                 }
  494.  
  495.                                 if (nchars-- == 0)
  496.                                         return FALSE;
  497.                         }
  498.                 }
  499.                 else                    /* Not closure.*/
  500.                 {
  501.                         /* The only way we'd get a BOL metacharacter
  502.                          * at this point is at the end of the reversed pattern.
  503.                          * The only way we'd get an EOL metacharacter
  504.                          * here is at the end of a regular pattern.
  505.                          * So if we match one or the other, and are at
  506.                          * the appropriate position, we are guaranteed success
  507.                          * (since the next pattern character has to be MCNIL).
  508.                          * Before we report success, however, we back up by
  509.                          * one character, so as to leave the cursor in the
  510.                          * correct position.  For example, a search for ")$"
  511.                          * will leave the cursor at the end of the line, while
  512.                          * a search for ")<NL>" will leave the cursor at the
  513.                          * beginning of the next line.  This follows the
  514.                          * notion that the meta-character '$' (and likewise
  515.                          * '^') match positions, not characters.
  516.                          */
  517.                         if (mcptr->mc_type == BOL)
  518.                         {       if (curoff == llength(curline))
  519.                                 {
  520.                                         nextch(&curline, &curoff,
  521.                                                    direct ^ REVERSE);
  522.                                         goto success;
  523.                                 }
  524.                                 else
  525.                                         return FALSE;
  526.                         }
  527.  
  528.                         if (mcptr->mc_type == EOL)
  529.                         {       if (curoff == 0)
  530.                                 {
  531.                                         nextch(&curline, &curoff,
  532.                                                    direct ^ REVERSE);
  533.                                         goto success;
  534.                                 }
  535.                                 else
  536.                                         return FALSE;
  537.                         }
  538.                         /* Neither BOL nor EOL, so go through
  539.                          * the meta-character equal function.
  540.                          */
  541.                         if (!mceq(c, mcptr))
  542.                                 return FALSE;
  543.                 }
  544.  
  545.                 /* Increment the length counter and
  546.                  * advance the pattern pointer.
  547.                  */
  548.                 matchlen++;
  549.                 mcptr++;
  550.         }                       /* End of mcptr loop.*/
  551.  
  552.         /* A SUCCESSFULL MATCH!!!
  553.          * Reset the "." pointers.
  554.          */
  555. success:
  556.         *pcwline = curline;
  557.         *pcwoff  = curoff;
  558.  
  559.         return TRUE;
  560. }
  561. #endif
  562.  
  563. /*
  564.  * scanner -- Search for a pattern in either direction.  If found,
  565.  *      reset the "." to be at the start or just after the match string,
  566.  *      and (perhaps) repaint the display.
  567.  *      Fast version using simplified version of Boyer and Moore
  568.  *      Software-Practice and Experience, vol 10, 501-506 (1980)
  569.  */
  570. int PASCAL NEAR scanner(patrn, direct, beg_or_end)
  571. char    *patrn;         /* string to scan for */
  572. int     direct;         /* which way to go.*/
  573. int     beg_or_end;     /* put point at beginning or end of pattern.*/
  574. {
  575.         register int    c;              /* character at current position */
  576.         register char   *patptr;        /* pointer into pattern */
  577.         LINE    *curline;               /* current line during scan */
  578.         int     curoff;                 /* position within current line */
  579.         LINE    *scanline;              /* current line during scanning */
  580.         int     scanoff;                /* position in scanned line */
  581.         int     jump;                   /* next offset */
  582.  
  583.         /* If we are going in reverse, then the 'end' is actually
  584.          * the beginning of the pattern.  Toggle it.
  585.          */
  586.         beg_or_end ^= direct;
  587.  
  588.         /* Set up local pointers to global ".".
  589.          */
  590.         curline = curwp->w_dotp;
  591.         curoff = curwp->w_doto;
  592.  
  593.         /* Scan each character until we hit the head link record.
  594.          * Get the character resolving newlines, offset
  595.          * by the pattern length, i.e. the last character of the
  596.          * potential match.
  597.          */
  598.         jump = patlenadd;
  599.  
  600.         while (!fbound(jump, &curline, &curoff, direct))
  601.         {
  602.                 /* Save the current position in case we match
  603.                  * the search string at this point.
  604.                  */
  605.                 matchline = curline;
  606.                 matchoff = curoff;
  607.  
  608.                 /* Setup scanning pointers.
  609.                  */
  610.                 scanline = curline;
  611.                 scanoff = curoff;
  612.                 patptr = &patrn[0];
  613.  
  614.                 /* Scan through the pattern for a match.
  615.                  */
  616.                 while ((c = *patptr++) != '\0')
  617.                         if (!eq((int) c, nextch(&scanline, &scanoff, direct)))
  618.                         {
  619.                                 jump = (direct == FORWARD)
  620.                                         ? lastchfjump
  621.                                         : lastchbjump;
  622.                                 goto fail;
  623.                         }
  624.  
  625.                 /* A SUCCESSFULL MATCH!!!
  626.                  * reset the global "." pointers
  627.                  */
  628.                 if (beg_or_end == PTEND)        /* at end of string */
  629.                 {
  630.                         curwp->w_dotp = scanline;
  631.                         curwp->w_doto = scanoff;
  632.                 }
  633.                 else            /* at beginning of string */
  634.                 {
  635.                         curwp->w_dotp = matchline;
  636.                         curwp->w_doto = matchoff;
  637.                 }
  638.  
  639.                 if (enterfolds)
  640.                         openoutfolds();
  641.  
  642.                 curwp->w_flag |= WFMOVE; /* Flag that we have moved.*/
  643.                 savematch();
  644.                 return TRUE;
  645.  
  646. fail:;                  /* continue to search */
  647.         }
  648.  
  649.         return FALSE;   /* We could not find a match */
  650. }
  651.  
  652. /*
  653.  * fbound -- Return information depending on whether we have hit a boundry
  654.  *      (and may therefore search no further) or if a trailing character
  655.  *      of the search string has been found.  See boundry() for search
  656.  *      restrictions.
  657.  */
  658. int PASCAL NEAR fbound(jump, pcurline, pcuroff, dir)
  659. LINE    **pcurline;
  660. int     *pcuroff, dir, jump;
  661. {
  662.         register int spare, curoff;
  663.         register LINE   *curline;
  664.  
  665.         curline = *pcurline;
  666.         curoff = *pcuroff;
  667.  
  668.         if (dir == FORWARD)
  669.         {
  670.                 while (jump != 0)
  671.                 {
  672.                         curoff += jump;
  673.                         spare = curoff - llength(curline);
  674.  
  675.                         while (spare > 0)
  676.                         {
  677.                                 if (enterfolds)
  678.                                         curline = curline->l_fp;
  679.                                 else
  680.                                         curline = lforw(curline);/* skip to next line */
  681.                                 curoff = spare - 1;
  682.                                 spare = curoff - llength(curline);
  683.                                 if (curline == curbp->b_linep)
  684.                                         return TRUE;    /* hit end of buffer */
  685.                         }
  686.  
  687.                         if (spare == 0)
  688.                                 jump = deltaf[(int) '\r'];
  689.                         else
  690.                                 jump = deltaf[(int) lgetc(curline, curoff)];
  691.                 }
  692.  
  693.                 /* The last character matches, so back up to start
  694.                  * of possible match.
  695.                  */
  696.                 curoff -= patlenadd;
  697.  
  698.                 while (curoff < 0)
  699.                 {
  700.                         if (enterfolds)
  701.                                 curline = curline->l_bp;
  702.                         else
  703.                                 curline = lback(curline);/* skip back a line */
  704.                         curoff += llength(curline) + 1;
  705.                 }
  706.  
  707.         }
  708.         else                    /* Reverse.*/
  709.         {
  710.                 jump++;         /* allow for offset in reverse */
  711.                 while (jump != 0)
  712.                 {
  713.                         curoff -= jump;
  714.                         while (curoff < 0)
  715.                         {
  716.                                 if (enterfolds)
  717.                                         curline = curline->l_bp;
  718.                                 else
  719.                                         curline = lback(curline);       /* skip back a line */
  720.                                 curoff += llength(curline) + 1;
  721.                                 if (curline == curbp->b_linep)
  722.                                         return TRUE;    /* hit end of buffer */
  723.                         }
  724.  
  725.                         if (curoff == llength(curline))
  726.                                 jump = deltab[(int) '\r'];
  727.                         else
  728.                                 jump = deltab[(int) lgetc(curline, curoff)];
  729.                 }
  730.  
  731.                 /* The last character matches, so back up to start
  732.                  * of possible match.
  733.                  */
  734.                 curoff += matchlen;
  735.                 spare = curoff - llength(curline);
  736.                 while (spare > 0)
  737.                 {
  738.                         if (enterfolds)
  739.                                 curline = curline->l_fp;
  740.                         else
  741.                                 curline = lforw(curline);/* skip back a line */
  742.                         curoff = spare - 1;
  743.                         spare = curoff - llength(curline);
  744.                 }
  745.         }
  746.  
  747.         *pcurline = curline;
  748.         *pcuroff = curoff;
  749.         return FALSE;
  750. }
  751.  
  752. /*
  753.  * setjtable -- Settting up search delta forward and delta backward
  754.  *      tables.  The reverse search string and string lengths are
  755.  *      set here, for table initialization and for substitution
  756.  *      purposes.  The default for any character to jump is the
  757.  *      pattern length.
  758.  */
  759. PASCAL NEAR setjtable(apat)
  760. char apat[];
  761. {
  762.         int             i;
  763.         
  764.         rvstrcpy(tap, apat);
  765.         patlenadd = (mlenold = matchlen = strlen(apat)) - 1;
  766.  
  767.         for (i = 0; i < HICHAR; i++)
  768.         {
  769.                 deltaf[i] = matchlen;
  770.                 deltab[i] = matchlen;
  771.         }
  772.  
  773.         /* Now put in the characters contained
  774.          * in the pattern, duplicating the CASE
  775.          */
  776.         for (i = 0; i < patlenadd; i++)
  777.         {
  778. #if     0
  779.                 /*
  780.                  * Debugging & tracing information.
  781.                  */
  782.                 mlwrite(TEXT82, (unsigned int) apat[i], patlenadd - i);
  783. /*                      "Considering %d with jump %d" */
  784.                 tgetc();
  785.                 if (isletter(apat[i]))
  786.                 {
  787.                         mlwrite(TEXT83, CHCASE((unsigned int) apat[i]));
  788. /*                              "Its other case is %d" */
  789.                         tgetc();
  790.                 }
  791. #endif
  792.                 if (isletter(apat[i]))
  793.                         deltaf[(unsigned int) CHCASE((unsigned int) apat[i])]
  794.                          = patlenadd - i;
  795.                 deltaf[(unsigned int) apat[i]] = patlenadd - i;
  796.  
  797.                 if (isletter(tap[i]))
  798.                         deltab[(unsigned int) CHCASE((unsigned int) tap[i])]
  799.                          = patlenadd - i;
  800.                 deltab[(unsigned int) tap[i]] = patlenadd - i;
  801.         }
  802.  
  803.         /* The last character will have the pattern length
  804.          * unless there are duplicates of it.  Get the number to
  805.          * jump from the arrays delta, and overwrite with zeroes in delta
  806.          * duplicating the CASE.
  807.          */
  808.         lastchfjump = patlenadd + deltaf[(unsigned int) apat[patlenadd]];
  809.         lastchbjump = patlenadd + deltab[(unsigned int) apat[0]];
  810.  
  811.         if (isletter(apat[patlenadd]))
  812.                 deltaf[(unsigned int) CHCASE(apat[patlenadd])] = 0;
  813.         deltaf[(int) apat[patlenadd]] = 0;
  814.  
  815.         if (isletter(apat[0]))
  816.                 deltab[(unsigned int) CHCASE(apat[0])] = 0;
  817.         deltab[(int) apat[0]] = 0;
  818.  
  819. }
  820.  
  821. /*
  822.  * eq -- Compare two characters.  The "bc" comes from the buffer, "pc"
  823.  *      from the pattern.  If we are not in EXACT mode, fold out the case.
  824.  */
  825. int PASCAL NEAR eq(bc, pc)
  826. register int    bc;
  827. register int    pc;
  828. {
  829.         if ((curwp->w_bufp->b_mode & MDEXACT) == 0)
  830.         {
  831.                 if (islower(bc))
  832.                         bc = CHCASE(bc);
  833.  
  834.                 if (islower(pc))
  835.                         pc = CHCASE(pc);
  836.  
  837.         }
  838.  
  839.         return(bc == pc);
  840. }
  841.  
  842. /*
  843.  * readpattern -- Read a pattern.  Stash it in apat.  If it is the
  844.  *      search string, create the reverse pattern and the magic
  845.  *      pattern, assuming we are in MAGIC mode (and defined that way).
  846.  *      Apat is not updated if the user types in an empty line.  If
  847.  *      the user typed an empty line, and there is no old pattern, it is
  848.  *      an error.  Display the old pattern, in the style of Jeff Lomicka.
  849.  *      There is some do-it-yourself control expansion.  Change to using
  850.  *      <META> to delimit the end-of-pattern to allow <NL>s in the search
  851.  *      string. 
  852.  */
  853. int PASCAL NEAR readpattern(prompt, apat, srch)
  854. char    *prompt;
  855. char    apat[];
  856. int     srch;
  857. {
  858.         int status;
  859.         char tpat[NPAT+20];
  860.  
  861.         strcpy(tpat, prompt);   /* copy prompt to output string */
  862.         strcat(tpat, " [");     /* build new prompt string */
  863.         expandp(&apat[0], &tpat[strlen(tpat)], NPAT/2); /* add old pattern */
  864.         strcat(tpat, "]<META>: ");
  865.  
  866.         /* Read a pattern.  Either we get one,
  867.          * or we just get the META charater, and use the previous pattern.
  868.          * Then, if it's the search string, make a reversed pattern.
  869.          * *Then*, make the meta-pattern, if we are defined that way.
  870.          */
  871.         if ((status = mltreply(tpat, tpat, NPAT, sterm)) == TRUE)
  872.         {
  873.                 strcpy(apat, tpat);
  874.  
  875.                 /* If we are doing the search string set the
  876.                  * delta tables.
  877.                  */
  878.                 if (srch)
  879.                         setjtable(apat);
  880.  
  881. #if     MAGIC
  882.                 /* Only make the meta-pattern if in magic mode,
  883.                  * since the pattern in question might have an
  884.                  * invalid meta combination.
  885.                  */
  886.                 if ((curwp->w_bufp->b_mode & MDMAGIC) == 0)
  887.                 {
  888.                         mcclear();
  889.                         rmcclear();
  890.                 }
  891.                 else
  892.                         status = srch? mcstr(): rmcstr();
  893. #endif
  894.         }
  895.         else if (status == FALSE && apat[0] != 0)       /* Old one */
  896.                 status = TRUE;
  897.  
  898.         return(status);
  899. }
  900.  
  901. /*
  902.  * savematch -- We found the pattern?  Let's save it away.
  903.  */
  904. PASCAL NEAR savematch()
  905. {
  906.         register char   *ptr;           /* pointer to last match string */
  907.         register int    j;
  908.         LINE            *curline;       /* line of last match */
  909.         int             curoff;         /* offset "      "    */
  910.  
  911.         /* Free any existing match string, then
  912.          * attempt to allocate a new one.
  913.          */
  914.         if (patmatch != NULL)
  915.                 free(patmatch);
  916.  
  917.         ptr = patmatch = malloc(matchlen + 1);
  918.  
  919.         if (ptr != NULL)
  920.         {
  921.                 curoff = matchoff;
  922.                 curline = matchline;
  923.  
  924.                 for (j = 0; j < matchlen; j++)
  925.                         *ptr++ = nextch(&curline, &curoff, FORWARD);
  926.  
  927.                 *ptr = '\0';
  928.         }
  929. }
  930.  
  931. /*
  932.  * rvstrcpy -- Reverse string copy.
  933.  */
  934. PASCAL NEAR rvstrcpy(rvstr, str)
  935. register char   *rvstr, *str;
  936. {
  937.         register int i;
  938.  
  939.         str += (i = strlen(str));
  940.  
  941.         while (i-- > 0)
  942.                 *rvstr++ = *--str;
  943.  
  944.         *rvstr = '\0';
  945. }
  946.  
  947. /*
  948.  * sreplace -- Search and replace.
  949.  */
  950. PASCAL NEAR sreplace(f, n)
  951. int f;          /* default flag */
  952. int n;          /* # of repetitions wanted */
  953. {
  954.         return(replaces(FALSE, f, n));
  955. }
  956.  
  957. /*
  958.  * qreplace -- search and replace with query.
  959.  */
  960. PASCAL NEAR qreplace(f, n)
  961. int f;          /* default flag */
  962. int n;          /* # of repetitions wanted */
  963. {
  964.         return(replaces(TRUE, f, n));
  965. }
  966.  
  967. /*
  968.  * replaces -- Search for a string and replace it with another
  969.  *      string.  Query might be enabled (according to kind).
  970.  */
  971. int PASCAL NEAR replaces(kind, f, n)
  972. int     kind;   /* Query enabled flag */
  973. int     f;      /* default flag */
  974. int     n;      /* # of repetitions wanted */
  975. {
  976.         register int status;    /* success flag on pattern inputs */
  977.         register int rlength;   /* length of replacement string */
  978.         register int numsub;    /* number of substitutions */
  979.         register int nummatch;  /* number of found matches */
  980.         int nlflag;             /* last char of search string a <NL>? */
  981.         int nlrepl;             /* was a replace done on the last line? */
  982.         char c;                 /* input char for query */
  983.         char tpat[NPAT];        /* temporary to hold search pattern */
  984.         LINE *origline;         /* original "." position */
  985.         int origoff;            /* and offset (for . query option) */
  986.         LINE *lastline;         /* position of last replace and */
  987.         int lastoff;            /* offset (for 'u' query option) */
  988.  
  989.         if (curbp->b_mode & MDVIEW)     /* don't allow this command if  */
  990.                 return(rdonly());       /* we are in read only mode     */
  991.  
  992.         /* Check for negative repetitions.
  993.          */
  994.         if (f && n < 0)
  995.                 return(FALSE);
  996.  
  997.         /* Ask the user for the text of a pattern.
  998.          */
  999.         if ((status = readpattern(
  1000.             (kind == FALSE ? TEXT84 : TEXT85), &pat[0], TRUE)) != TRUE)
  1001. /*                           "Replace" */
  1002. /*                                    "Query replace" */
  1003.                 return(status);
  1004.  
  1005.         /* Ask for the replacement string.
  1006.          */
  1007.         if ((status = readpattern(TEXT86, &rpat[0], FALSE)) == ABORT)
  1008. /*                                "with" */
  1009.                 return(status);
  1010.  
  1011.         /* Find the length of the replacement string.
  1012.          */
  1013.         rlength = strlen(&rpat[0]);
  1014.  
  1015.         /* Make sure we go down into folds. MJB: 13-Oct-89 */
  1016.         enterfolds = TRUE;
  1017.  
  1018.         /* Set up flags so we can make sure not to do a recursive
  1019.          * replace on the last line.
  1020.          */
  1021.         nlflag = (pat[matchlen - 1] == '\r');
  1022.         nlrepl = FALSE;
  1023.  
  1024.         if (kind)
  1025.         {
  1026.                 /* Build query replace question string.
  1027.                  */
  1028.                 strcpy(tpat, TEXT87);
  1029. /*                           "Replace '" */
  1030.                 expandp(&pat[0], &tpat[strlen(tpat)], NPAT/3);
  1031.                 strcat(tpat, TEXT88);
  1032. /*                           "' with '" */
  1033.                 expandp(&rpat[0], &tpat[strlen(tpat)], NPAT/3);
  1034.                 strcat(tpat, "'? ");
  1035.  
  1036.                 /* Initialize last replaced pointers.
  1037.                  */
  1038.                 lastline = NULL;
  1039.                 lastoff = 0;
  1040.         }
  1041.  
  1042.         /* Save original . position, init the number of matches and
  1043.          * substitutions, and scan through the file.
  1044.          */
  1045.         origline = curwp->w_dotp;
  1046.         origoff = curwp->w_doto;
  1047.         numsub = 0;
  1048.         nummatch = 0;
  1049.  
  1050.         while ( (f == FALSE || n > nummatch) &&
  1051.                 (nlflag == FALSE || nlrepl == FALSE) )
  1052.         {
  1053.                 /* Search for the pattern.
  1054.                  * If we search with a regular expression,
  1055.                  * matchlen is reset to the true length of
  1056.                  * the matched string.
  1057.                  */
  1058. #if     MAGIC
  1059.                 if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
  1060.                 {
  1061.                         if (!mcscanner(&mcpat[0], FORWARD, PTBEG))
  1062.                                 break;
  1063.                 }
  1064.                 else
  1065. #endif
  1066.                         if (!scanner(&pat[0], FORWARD, PTBEG))
  1067.                                 break;          /* all done */
  1068.  
  1069.                 ++nummatch;     /* Increment # of matches */
  1070.  
  1071.                 /* Check if we are on the last line.
  1072.                  */
  1073.                 nlrepl = (lforw(curwp->w_dotp) == curwp->w_bufp->b_linep);
  1074.  
  1075.                 /* Check for query.
  1076.                  */
  1077.                 if (kind)
  1078.                 {
  1079.                         /* Get the query.
  1080.                          */
  1081. pprompt:                mlwrite(&tpat[0], &pat[0], &rpat[0]);
  1082. qprompt:
  1083.                         update(TRUE);  /* show the proposed place to change */
  1084.                         c = tgetc();                    /* and input */
  1085.                         mlwrite("");                    /* and clear it */
  1086.  
  1087.                         /* And respond appropriately.
  1088.                          */
  1089.                         switch (c)
  1090.                         {
  1091. #if     FRENCH
  1092.                                 case 'o':       /* yes, substitute */
  1093.                                 case 'O':
  1094. #endif
  1095.                                 case 'y':       /* yes, substitute */
  1096.                                 case 'Y':
  1097.                                 case ' ':
  1098.                                         break;
  1099.  
  1100.                                 case 'n':       /* no, onword */
  1101.                                 case 'N':
  1102.                                         forwchar(FALSE, 1);
  1103.                                         continue;
  1104.  
  1105.                                 case '!':       /* yes/stop asking */
  1106.                                         kind = FALSE;
  1107.                                         break;
  1108.  
  1109.                                 case 'u':       /* undo last and re-prompt */
  1110.                                 case 'U':
  1111.                                         /* Restore old position.
  1112.                                          */
  1113.                                         if (lastline == NULL)
  1114.                                         {
  1115.                                                 /* There is nothing to undo.
  1116.                                                  */
  1117.                                                 TTbeep();
  1118.                                                 goto pprompt;
  1119.                                         }
  1120.                                         curwp->w_dotp = lastline;
  1121.                                         curwp->w_doto = lastoff;
  1122.                                         lastline = NULL;
  1123.                                         lastoff = 0;
  1124.  
  1125.                                         /* Delete the new string.
  1126.                                          */
  1127.                                         backchar(FALSE, rlength);
  1128.                                         status = delins(rlength, patmatch, FALSE);
  1129.                                         if (status != TRUE)
  1130.                                                 return(status);
  1131.  
  1132.                                         /* Record one less substitution,
  1133.                                          * backup, save our place, and
  1134.                                          * reprompt.
  1135.                                          */
  1136.                                         --numsub;
  1137.                                         backchar(FALSE, mlenold);
  1138.                                         matchline = curwp->w_dotp;
  1139.                                         matchoff  = curwp->w_doto;
  1140.                                         goto pprompt;
  1141.  
  1142.                                 case '.':       /* abort! and return */
  1143.                                         /* restore old position */
  1144.                                         curwp->w_dotp = origline;
  1145.                                         curwp->w_doto = origoff;
  1146.                                         curwp->w_flag |= WFMOVE;
  1147.  
  1148.                                 case BELL:      /* abort! and stay */
  1149.                                         mlwrite(TEXT89);
  1150. /*                                              "Aborted!" */
  1151.                                         return(FALSE);
  1152.  
  1153.                                 default:        /* bitch and beep */
  1154.                                         TTbeep();
  1155.  
  1156.                                 case '?':       /* help me */
  1157.                                         mlwrite(TEXT90);
  1158. /*"(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: "*/
  1159.                                         goto qprompt;
  1160.  
  1161.                         }       /* end of switch */
  1162.                 }       /* end of "if kind" */
  1163.  
  1164.                 /* if this is the point origin, flag so we a can reset it */
  1165.                 if (curwp->w_dotp == origline) {
  1166.                         origline = NULL;
  1167.                         lastline = curwp->w_dotp->l_bp;
  1168.                 }
  1169.  
  1170.                 /*
  1171.                  * Delete the sucker, and insert its
  1172.                  * replacement.
  1173.                  */
  1174.                 status = delins(matchlen, &rpat[0], TRUE);
  1175.                 if (origline == NULL) {
  1176.                         origline = lastline->l_fp;
  1177.                         origoff = 0;
  1178.                 }
  1179.  
  1180.                 if (status != TRUE)
  1181.                         return(status);
  1182.  
  1183.                 /* Save our position, since we may undo this.
  1184.                  * If we are not querying, check to make sure
  1185.                  * that we didn't replace an empty string
  1186.                  * (possible in MAGIC mode), because we'll
  1187.                  * infinite loop.
  1188.                  */
  1189.                 if (kind)
  1190.                 {
  1191.                         lastline = curwp->w_dotp;
  1192.                         lastoff = curwp->w_doto;
  1193.                 }
  1194.                 else if (matchlen == 0)
  1195.                 {
  1196.                         mlwrite(TEXT91);
  1197. /*                              "Empty string replaced, stopping." */
  1198.                         return(FALSE);
  1199.                 }
  1200.  
  1201.                 numsub++;       /* increment # of substitutions */
  1202.         }
  1203.  
  1204.         /* And report the results.
  1205.          */
  1206.         mlwrite(TEXT92, numsub);
  1207. /*              "%d substitutions" */
  1208.         return(TRUE);
  1209. }
  1210.  
  1211. /*
  1212.  * delins -- Delete a specified length from the current point
  1213.  *      then either insert the string directly, or make use of
  1214.  *      replacement meta-array.
  1215.  */
  1216. PASCAL NEAR delins(dlength, instr, use_meta)
  1217. int     dlength;
  1218. char    *instr;
  1219. int     use_meta;
  1220. {
  1221.         int     status;
  1222. #if     MAGIC
  1223.         RMC     *rmcptr;
  1224. #endif
  1225.  
  1226.         /* Zap what we gotta,
  1227.          * and insert its replacement.
  1228.          */
  1229.         if ((status = ldelete((long) dlength, FALSE, FALSE, FALSE)) != TRUE)
  1230.                 mlwrite(TEXT93);
  1231. /*                      "%%ERROR while deleting" */
  1232.         else
  1233. #if     MAGIC
  1234.                 if ((rmagical && use_meta) &&
  1235.                      (curwp->w_bufp->b_mode & MDMAGIC) != 0)
  1236.                 {
  1237.                         rmcptr = &rmcpat[0];
  1238.                         while (rmcptr->mc_type != MCNIL && status == TRUE)
  1239.                         {
  1240.                                 if (rmcptr->mc_type == LITCHAR)
  1241.                                         status = linstr(rmcptr->rstr);
  1242.                                 else
  1243.                                         status = linstr(patmatch);
  1244.                                 rmcptr++;
  1245.                         }
  1246.                 }
  1247.                 else
  1248. #endif
  1249.                         status = linstr(instr);
  1250.  
  1251.         return(status);
  1252. }
  1253.  
  1254. /*
  1255.  * expandp -- Expand control key sequences for output.
  1256.  */
  1257. PASCAL NEAR expandp(srcstr, deststr, maxlength)
  1258. char    *srcstr;        /* string to expand */
  1259. char    *deststr;       /* destination of expanded string */
  1260. int     maxlength;      /* maximum chars in destination */
  1261. {
  1262.         unsigned char c;        /* current char to translate */
  1263.  
  1264.         /* Scan through the string.
  1265.          */
  1266.         while ((c = *srcstr++) != 0)
  1267.         {
  1268.                 if (c == '\r')          /* it's a newline */
  1269.                 {
  1270.                         *deststr++ = '<';
  1271.                         *deststr++ = 'N';
  1272.                         *deststr++ = 'L';
  1273.                         *deststr++ = '>';
  1274.                         maxlength -= 4;
  1275.                 }
  1276.                 else if (c < 0x20 || c == 0x7f) /* control character */
  1277.                 {
  1278.                         *deststr++ = '^';
  1279.                         *deststr++ = c ^ 0x40;
  1280.                         maxlength -= 2;
  1281.                 }
  1282.                 else if (c == '%')
  1283.                 {
  1284.                         *deststr++ = '%';
  1285.                         *deststr++ = '%';
  1286.                         maxlength -= 2;
  1287.                 }
  1288.                 else                    /* any other character */
  1289.                 {
  1290.                         *deststr++ = c;
  1291.                         maxlength--;
  1292.                 }
  1293.  
  1294.                 /* check for maxlength */
  1295.                 if (maxlength < 4)
  1296.                 {
  1297.                         *deststr++ = '$';
  1298.                         *deststr = '\0';
  1299.                         return(FALSE);
  1300.                 }
  1301.         }
  1302.         *deststr = '\0';
  1303.         return(TRUE);
  1304. }
  1305.  
  1306. /*
  1307.  * boundry -- Return information depending on whether we may search no
  1308.  *      further.  Beginning of file and end of file are the obvious
  1309.  *      cases, but we may want to add further optional boundry restrictions
  1310.  *      in future, a' la VMS EDT.  At the moment, just return TRUE or
  1311.  *      FALSE depending on if a boundry is hit (ouch).
  1312.  */
  1313. int PASCAL NEAR boundry(curline, curoff, dir)
  1314. LINE    *curline;
  1315. int     curoff, dir;
  1316. {
  1317.         register int    border;
  1318.  
  1319.         if (dir == FORWARD)
  1320.         {
  1321.                 border = (curoff == llength(curline)) &&
  1322.                          (lforw(curline) == curbp->b_linep);
  1323.         }
  1324.         else
  1325.         {
  1326.                 border = (curoff == 0) &&
  1327.                          (lback(curline) == curbp->b_linep);
  1328.         }
  1329.         return(border);
  1330. }
  1331.  
  1332. /*
  1333.  * nextch -- retrieve the next/previous character in the buffer,
  1334.  *      and advance/retreat the point.
  1335.  *      The order in which this is done is significant, and depends
  1336.  *      upon the direction of the search.  Forward searches look at
  1337.  *      the current character and move, reverse searches move and
  1338.  *      look at the character.
  1339.  */
  1340. int PASCAL NEAR nextch(pcurline, pcuroff, dir)
  1341. LINE    **pcurline;
  1342. int     *pcuroff;
  1343. int     dir;
  1344. {
  1345.         register LINE   *curline;
  1346.         register int    curoff;
  1347.         register int    c;
  1348.  
  1349.         curline = *pcurline;
  1350.         curoff = *pcuroff;
  1351.  
  1352.         if (dir == FORWARD)
  1353.         {
  1354.                 if (curoff == llength(curline))         /* if at EOL */
  1355.                 {
  1356.                         if (enterfolds)
  1357.                                 curline = curline->l_fp;
  1358.                         else
  1359.                                 curline = lforw(curline);       /* skip to next line */
  1360.                         curoff = 0;
  1361.                         c = '\r';                       /* and return a <NL> */
  1362.                 }
  1363.                 else
  1364.                         c = lgetc(curline, curoff++);   /* get the char */
  1365.         }
  1366.         else                    /* Reverse.*/
  1367.         {
  1368.                 if (curoff == 0)
  1369.                 {
  1370.                         if (enterfolds)
  1371.                                 curline = curline->l_bp;
  1372.                         else
  1373.                                 curline = lback(curline);
  1374.                         curoff = llength(curline);
  1375.                         c = '\r';
  1376.                 }
  1377.                 else
  1378.                         c = lgetc(curline, --curoff);
  1379.  
  1380.         }
  1381.         *pcurline = curline;
  1382.         *pcuroff = curoff;
  1383.  
  1384.         return(c);
  1385. }
  1386.  
  1387. #if     MAGIC
  1388. /*
  1389.  * mcstr -- Set up the 'magic' array.  The closure symbol is taken as
  1390.  *      a literal character when (1) it is the first character in the
  1391.  *      pattern, and (2) when preceded by a symbol that does not allow
  1392.  *      closure, such as a newline, beginning of line symbol, or another
  1393.  *      closure symbol.
  1394.  *
  1395.  *      Coding comment (jmg):  yes, i know i have gotos that are, strictly
  1396.  *      speaking, unnecessary.  But right now we are so cramped for
  1397.  *      code space that i will grab what i can in order to remain
  1398.  *      within the 64K limit.  C compilers actually do very little
  1399.  *      in the way of optimizing - they expect you to do that.
  1400.  */
  1401. int PASCAL NEAR mcstr()
  1402. {
  1403.         MC      *mcptr, *rtpcm;
  1404.         char    *patptr;
  1405.         int     mj;
  1406.         int     pchr;
  1407.         int     status = TRUE;
  1408.         int     does_closure = FALSE;
  1409.  
  1410.         /* If we had metacharacters in the MC array previously,
  1411.          * free up any bitmaps that may have been allocated, and
  1412.          * reset magical.
  1413.          */
  1414.         if (magical)
  1415.                 mcclear();
  1416.  
  1417.         mj = 0;
  1418.         mcptr = &mcpat[0];
  1419.         patptr = &pat[0];
  1420.  
  1421.         while ((pchr = *patptr) && status)
  1422.         {
  1423.                 switch (pchr)
  1424.                 {
  1425.                         case MC_CCL:
  1426.                                 status = cclmake(&patptr, mcptr);
  1427.                                 magical = TRUE;
  1428.                                 does_closure = TRUE;
  1429.                                 break;
  1430.                         case MC_BOL:
  1431.                                 if (mj != 0)
  1432.                                         goto litcase;
  1433.  
  1434.                                 mcptr->mc_type = BOL;
  1435.                                 magical = TRUE;
  1436.                                 break;
  1437.                         case MC_EOL:
  1438.                                 if (*(patptr + 1) != '\0')
  1439.                                         goto litcase;
  1440.  
  1441.                                 mcptr->mc_type = EOL;
  1442.                                 magical = TRUE;
  1443.                                 break;
  1444.                         case MC_ANY:
  1445.                                 mcptr->mc_type = ANY;
  1446.                                 magical = TRUE;
  1447.                                 does_closure = TRUE;
  1448.                                 break;
  1449.                         case MC_CLOSURE:
  1450.                                 /* Does the closure symbol mean closure here?
  1451.                                  * If so, back up to the previous element
  1452.                                  * and indicate it is enclosed.
  1453.                                  */
  1454.                                 if (!does_closure)
  1455.                                         goto litcase;
  1456.                                 mj--;
  1457.                                 mcptr--;
  1458.                                 mcptr->mc_type |= CLOSURE;
  1459.                                 magical = TRUE;
  1460.                                 does_closure = FALSE;
  1461.                                 break;
  1462.  
  1463.                         /* Note: no break between MC_ESC case and the default.
  1464.                          */
  1465.                         case MC_ESC:
  1466.                                 if (*(patptr + 1) != '\0')
  1467.                                 {
  1468.                                         pchr = *++patptr;
  1469.                                         magical = TRUE;
  1470.                                 }
  1471.                         default:
  1472. litcase:                        mcptr->mc_type = LITCHAR;
  1473.                                 mcptr->u.lchar = pchr;
  1474.                                 does_closure = (pchr != '\r');
  1475.                                 break;
  1476.                 }               /* End of switch.*/
  1477.                 mcptr++;
  1478.                 patptr++;
  1479.                 mj++;
  1480.         }               /* End of while.*/
  1481.  
  1482.         /* Close off the meta-string.
  1483.          */
  1484.         mcptr->mc_type = MCNIL;
  1485.  
  1486.         /* Set up the reverse array, if the status is good.  Please note the
  1487.          * structure assignment - your compiler may not like that.
  1488.          * If the status is not good, nil out the meta-pattern.
  1489.          * The only way the status would be bad is from the cclmake()
  1490.          * routine, and the bitmap for that member is guarenteed to be
  1491.          * freed.  So we stomp a MCNIL value there, and call mcclear()
  1492.          * to free any other bitmaps.
  1493.          */
  1494.         if (status)
  1495.         {
  1496.                 rtpcm = &tapcm[0];
  1497.                 while (--mj >= 0)
  1498.                 {
  1499. #if     LATTICE
  1500.                         movmem(--mcptr, rtpcm++, sizeof (MC));
  1501. #else
  1502.                         *rtpcm++ = *--mcptr;
  1503. #endif
  1504.                 }
  1505.                 rtpcm->mc_type = MCNIL;
  1506.         }
  1507.         else
  1508.         {
  1509.                 (--mcptr)->mc_type = MCNIL;
  1510.                 mcclear();
  1511.         }
  1512.  
  1513.         return(status);
  1514. }
  1515.  
  1516. /*
  1517.  * rmcstr -- Set up the replacement 'magic' array.  Note that if there
  1518.  *      are no meta-characters encountered in the replacement string,
  1519.  *      the array is never actually created - we will just use the
  1520.  *      character array rpat[] as the replacement string.
  1521.  */
  1522. PASCAL NEAR rmcstr()
  1523. {
  1524.         RMC     *rmcptr;
  1525.         char    *patptr;
  1526.         int     status = TRUE;
  1527.         int     mj;
  1528.  
  1529.         patptr = &rpat[0];
  1530.         rmcptr = &rmcpat[0];
  1531.         mj = 0;
  1532.         rmagical = FALSE;
  1533.  
  1534.         while (*patptr && status == TRUE)
  1535.         {
  1536.                 switch (*patptr)
  1537.                 {
  1538.                         case MC_DITTO:
  1539.  
  1540.                                 /* If there were non-magical characters
  1541.                                  * in the string before reaching this
  1542.                                  * character, plunk it in the replacement
  1543.                                  * array before processing the current
  1544.                                  * meta-character.
  1545.                                  */
  1546.                                 if (mj != 0)
  1547.                                 {
  1548.                                         rmcptr->mc_type = LITCHAR;
  1549.                                         if ((rmcptr->rstr = malloc(mj + 1)) == NULL)
  1550.                                         {
  1551.                                                 mlwrite(TEXT94);
  1552. /*                                                      "%%Out of memory" */
  1553.                                                 status = FALSE;
  1554.                                                 break;
  1555.                                         }
  1556.                                         bytecopy(rmcptr->rstr, patptr - mj, mj);
  1557.                                         rmcptr++;
  1558.                                         mj = 0;
  1559.                                 }
  1560.                                 rmcptr->mc_type = DITTO;
  1561.                                 rmcptr++;
  1562.                                 rmagical = TRUE;
  1563.                                 break;
  1564.  
  1565.                         case MC_ESC:
  1566.                                 rmcptr->mc_type = LITCHAR;
  1567.  
  1568.                                 /* We malloc mj plus two here, instead
  1569.                                  * of one, because we have to count the
  1570.                                  * current character.
  1571.                                  */
  1572.                                 if ((rmcptr->rstr = malloc(mj + 2)) == NULL)
  1573.                                 {
  1574.                                         mlwrite(TEXT94);
  1575. /*                                              "%%Out of memory" */
  1576.                                         status = FALSE;
  1577.                                         break;
  1578.                                 }
  1579.  
  1580.                                 bytecopy(rmcptr->rstr, patptr - mj, mj + 1);
  1581.  
  1582.                                 /* If MC_ESC is not the last character
  1583.                                  * in the string, find out what it is
  1584.                                  * escaping, and overwrite the last
  1585.                                  * character with it.
  1586.                                  */
  1587.                                 if (*(patptr + 1) != '\0')
  1588.                                         *((rmcptr->rstr) + mj) = *++patptr;
  1589.  
  1590.                                 rmcptr++;
  1591.                                 mj = 0;
  1592.                                 rmagical = TRUE;
  1593.                                 break;
  1594.  
  1595.                         default:
  1596.                                 mj++;
  1597.                 }
  1598.                 patptr++;
  1599.         }
  1600.  
  1601.         if (rmagical && mj > 0)
  1602.         {
  1603.                 rmcptr->mc_type = LITCHAR;
  1604.                 if ((rmcptr->rstr = malloc(mj + 1)) == NULL)
  1605.                 {
  1606.                         mlwrite(TEXT94);
  1607. /*                              "%%Out of memory" */
  1608.                         status = FALSE;
  1609.                 }
  1610.                 bytecopy(rmcptr->rstr, patptr - mj, mj);
  1611.                 rmcptr++;
  1612.         }
  1613.  
  1614.         rmcptr->mc_type = MCNIL;
  1615. }
  1616.  
  1617. /*
  1618.  * mcclear -- Free up any CCL bitmaps, and MCNIL the MC search arrays.
  1619.  */
  1620. PASCAL NEAR mcclear()
  1621. {
  1622.         register MC     *mcptr;
  1623.  
  1624.         mcptr = &mcpat[0];
  1625.  
  1626.         while (mcptr->mc_type != MCNIL)
  1627.         {
  1628.                 if ((mcptr->mc_type & MASKCL) == CCL ||
  1629.                     (mcptr->mc_type & MASKCL) == NCCL)
  1630.                         free(mcptr->u.cclmap);
  1631.                 mcptr++;
  1632.         }
  1633.         mcpat[0].mc_type = tapcm[0].mc_type = MCNIL;
  1634.         magical = FALSE;
  1635. }
  1636.  
  1637. /*
  1638.  * rmcclear -- Free up any strings, and MCNIL the RMC array.
  1639.  */
  1640. PASCAL NEAR rmcclear()
  1641. {
  1642.         register RMC    *rmcptr;
  1643.  
  1644.         rmcptr = &rmcpat[0];
  1645.  
  1646.         while (rmcptr->mc_type != MCNIL)
  1647.         {
  1648.                 if (rmcptr->mc_type == LITCHAR)
  1649.                         free(rmcptr->rstr);
  1650.                 rmcptr++;
  1651.         }
  1652.  
  1653.         rmcpat[0].mc_type = MCNIL;
  1654. }
  1655.  
  1656. /*
  1657.  * mceq -- meta-character equality with a character.  In Kernighan & Plauger's
  1658.  *      Software Tools, this is the function omatch(), but i felt there
  1659.  *      were too many functions with the 'match' name already.
  1660.  */
  1661. int PASCAL NEAR mceq(bc, mt)
  1662. int     bc;
  1663. MC      *mt;
  1664. {
  1665.         register int result;
  1666.  
  1667.         switch (mt->mc_type & MASKCL)
  1668.         {
  1669.                 case LITCHAR:
  1670.                         result = eq(bc, (int) mt->u.lchar);
  1671.                         break;
  1672.  
  1673.                 case ANY:
  1674.                         result = (bc != '\r');
  1675.                         break;
  1676.  
  1677.                 case CCL:
  1678.                         if (!(result = biteq(bc, mt->u.cclmap)))
  1679.                         {
  1680.                                 if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1681.                                     (isletter(bc)))
  1682.                                 {
  1683.                                         result = biteq(CHCASE(bc), mt->u.cclmap);
  1684.                                 }
  1685.                         }
  1686.                         break;
  1687.  
  1688.                 case NCCL:
  1689.                         result = !biteq(bc, mt->u.cclmap);
  1690.  
  1691.                         if ((curwp->w_bufp->b_mode & MDEXACT) == 0 &&
  1692.                             (isletter(bc)))
  1693.                         {
  1694.                                 result &= !biteq(CHCASE(bc), mt->u.cclmap);
  1695.                         }
  1696.                         break;
  1697.  
  1698.                 default:
  1699.                         mlwrite(TEXT95, mt->mc_type);
  1700. /*                              "%%mceq: what is %d?" */
  1701.                         result = FALSE;
  1702.                         break;
  1703.  
  1704.         }       /* End of switch.*/
  1705.  
  1706.         return(result);
  1707. }
  1708.  
  1709. /*
  1710.  * cclmake -- create the bitmap for the character class.
  1711.  *      ppatptr is left pointing to the end-of-character-class character,
  1712.  *      so that a loop may automatically increment with safety.
  1713.  */
  1714. int PASCAL NEAR cclmake(ppatptr, mcptr)
  1715. char    **ppatptr;
  1716. MC      *mcptr;
  1717. {
  1718.         BITMAP          bmap;
  1719.         register char   *patptr;
  1720.         register int    pchr, ochr;
  1721.  
  1722.         if ((bmap = clearbits()) == NULL)
  1723.         {
  1724.                 mlwrite(TEXT94);
  1725. /*                      "%%Out of memory" */
  1726.                 return FALSE;
  1727.         }
  1728.  
  1729.         mcptr->u.cclmap = bmap;
  1730.         patptr = *ppatptr;
  1731.  
  1732.         /*
  1733.          * Test the initial character(s) in ccl for
  1734.          * special cases - negate ccl, or an end ccl
  1735.          * character as a first character.  Anything
  1736.          * else gets set in the bitmap.
  1737.          */
  1738.         if (*++patptr == MC_NCCL)
  1739.         {
  1740.                 patptr++;
  1741.                 mcptr->mc_type = NCCL;
  1742.         }
  1743.         else
  1744.                 mcptr->mc_type = CCL;
  1745.  
  1746.         if ((ochr = *patptr) == MC_ECCL)
  1747.         {
  1748.                 mlwrite(TEXT96);
  1749. /*                      "%%No characters in character class" */
  1750.                 free(bmap);
  1751.                 return(FALSE);
  1752.         }
  1753.         else
  1754.         {
  1755.                 if (ochr == MC_ESC)
  1756.                         ochr = *++patptr;
  1757.  
  1758.                 setbit(ochr, bmap);
  1759.                 patptr++;
  1760.         }
  1761.  
  1762.         while (ochr != '\0' && (pchr = *patptr) != MC_ECCL)
  1763.         {
  1764.                 switch (pchr)
  1765.                 {
  1766.                         /* Range character loses its meaning
  1767.                          * if it is the last character in
  1768.                          * the class.
  1769.                          */
  1770.                         case MC_RCCL:
  1771.                                 if (*(patptr + 1) == MC_ECCL)
  1772.                                         setbit(pchr, bmap);
  1773.                                 else
  1774.                                 {
  1775.                                         pchr = *++patptr;
  1776.                                         while (++ochr <= pchr)
  1777.                                                 setbit(ochr, bmap);
  1778.                                 }
  1779.                                 break;
  1780.  
  1781.                         /* Note: no break between case MC_ESC and the default.
  1782.                          */
  1783.                         case MC_ESC:
  1784.                                 pchr = *++patptr;
  1785.                         default:
  1786.                                 setbit(pchr, bmap);
  1787.                                 break;
  1788.                 }
  1789.                 patptr++;
  1790.                 ochr = pchr;
  1791.         }
  1792.  
  1793.         *ppatptr = patptr;
  1794.  
  1795.         if (ochr == '\0')
  1796.         {
  1797.                 mlwrite(TEXT97);
  1798. /*                      "%%Character class not ended" */
  1799.                 free(bmap);
  1800.                 return FALSE;
  1801.         }
  1802.         return TRUE;
  1803. }
  1804.  
  1805. /*
  1806.  * biteq -- is the character in the bitmap?
  1807.  */
  1808. int PASCAL NEAR biteq(bc, cclmap)
  1809. int     bc;
  1810. BITMAP  cclmap;
  1811. {
  1812.         if (bc >= HICHAR)
  1813.                 return FALSE;
  1814.  
  1815.         return( (*(cclmap + (bc >> 3)) & BIT(bc & 7))? TRUE: FALSE );
  1816. }
  1817.  
  1818. /*
  1819.  * clearbits -- Allocate and zero out a CCL bitmap.
  1820.  */
  1821. BITMAP PASCAL NEAR clearbits()
  1822. {
  1823.         BITMAP          cclstart, cclmap;
  1824.         register int    j;
  1825.  
  1826.         if ((cclmap = cclstart = (BITMAP) malloc(HIBYTE)) != NULL)
  1827.                 for (j = 0; j < (HIBYTE); j++)
  1828.                         *cclmap++ = 0;
  1829.  
  1830.         return(cclstart);
  1831. }
  1832.  
  1833. /*
  1834.  * setbit -- Set a bit (ON only) in the bitmap.
  1835.  */
  1836. PASCAL NEAR setbit(bc, cclmap)
  1837. int     bc;
  1838. BITMAP  cclmap;
  1839. {
  1840.         if (bc < HICHAR)
  1841.                 *(cclmap + (bc >> 3)) |= BIT(bc & 7);
  1842. }
  1843. #endif
  1844.